#include "sCard.h"
#include <stdio.h>


#ifdef _SDCARD

// #define _CARD_LOG

#ifdef _CARD_LOG
    static const char *TAG = "sCard";
    #define CARD_LOG_DEBUG(TAG,...) EASYLOG_ERROR(TAG,##__VA_ARGS__);
#else
    #define CARD_LOG_DEBUG(TAG,...)
#endif

static sCardOps_t *sCardOps = NULL;
static sCardOps_t sCardOpsStruct = {
    NULL,
    NULL,
    NULL,
    NULL,
    sCARD_TYPE_ERROR,
};
/**
 * @brief set sCard Speed is Low
 * 
 * @return int8_t 
 */
static int8_t sCardSetSpeedLow(void){
    if(NULL == sCardOps){
        CARD_LOG_DEBUG(TAG,"sCardOps is NULL,Check sCardInit");
        return -1;
    }
    sCardOps->sCardSetSpeed(0);
    return 0;
}

/**
 * @brief set sCard Speed is High
 * 
 * @return int8_t 
 */
static int8_t sCardSetSpeedHigh(void){
    if(NULL == sCardOps){
        CARD_LOG_DEBUG(TAG,"sCardOps is NULL,Check sCardInit");
        return -1;
    }
    sCardOps->sCardSetSpeed(1);
    return 0;
}

/**
 * @brief wait sCard is Ready
 * 
 * @return int8_t 
 */
static int8_t sCardWaitReady(void){
    if(NULL == sCardOps){
        CARD_LOG_DEBUG(TAG,"sCardOps is NULL,Check sCardInit");
        return -1;
    }
    uint32_t wait = 0;
    for(;wait < 0xFFFE;wait ++){
        if(sCardOps->sCardReadWrite(sCARD_DUMMY_BYTE) == sCARD_DUMMY_BYTE){
            return 0;
        }
    }
    CARD_LOG_DEBUG(TAG,"sCard Wait Ready Timeout");
    return 1;
}

/**
 * @brief disable sCard
 * 
 * @return int8_t 
 */
static int8_t sCardDisable(void){
    if(NULL == sCardOps){
        CARD_LOG_DEBUG(TAG,"sCardOps is NULL,Check sCardInit");
        return -1;
    }
    sCardOps->sCardStatusSet(1);
    sCardOps->sCardReadWrite(sCARD_DUMMY_BYTE);
    return 0;
}

/**
 * @brief enable sCard
 * 
 * @return int8_t 
 */
static int8_t sCardEnable(void){
    if(NULL == sCardOps){
        CARD_LOG_DEBUG(TAG,"sCardOps is NULL,Check sCardInit");
        return -1;
    }
    int8_t status;
    sCardOps->sCardStatusSet(0);
    if(sCardWaitReady() == 0){
        status = 0;
    }else{
        sCardDisable();
        CARD_LOG_DEBUG(TAG,"sCardOps Enable Fail");
        status = -1;
    }
    return status;
}

/**
 * @brief get sCard Response
 * 
 * @param res need compare response
 * @return sCardMSDStatus_t 
 */
static sCardMSDStatus_t sCardGetResponse(uint8_t res){
    if(NULL == sCardOps){
        CARD_LOG_DEBUG(TAG,"sCardOps is NULL,Check sCardInit");
        return sCARD_MSD_OTHER_ERROR;
    }
    uint16_t wait = 0xFFFF;
    sCardMSDStatus_t status;
    while ((sCardOps->sCardReadWrite(sCARD_DUMMY_BYTE) != res) && wait){
		wait--; 
	}
    if(wait == 0){
        CARD_LOG_DEBUG(TAG,"sCard Get Response Timeout");
        status = sCARD_MSD_RES_FAIL;
    }else{
        status = sCARD_MSD_RES_OK;
    }
    return status;
}

/**
 * @brief sCard Read Date
 * 
 * @param date date buffer
 * @param size date size
 * @return int8_t 
 */
static int8_t sCardReadDate(void *date,uint16_t size){
    if(NULL == sCardOps){
        CARD_LOG_DEBUG(TAG,"sCardOps is NULL,Check sCardInit");
        return -1;
    }
    if(sCardGetResponse(sCARD_SINGLE_BLOCK_READ) != sCARD_MSD_RES_OK){
        return 1;
    }
    int8_t *r_date = (int8_t *)date;
    /*date read*/
    while(size --){
        *r_date = sCardOps->sCardReadWrite(sCARD_DUMMY_BYTE);
        r_date ++;
    }
    sCardOps->sCardReadWrite(sCARD_DUMMY_BYTE);
    sCardOps->sCardReadWrite(sCARD_DUMMY_BYTE);
    return 0;
}

/**
 * @brief sCard Write Date
 * 
 * @param date date buffer
 * @param cmd date Command
 * @return int8_t 
 */
static int8_t sCardWriteDate(void *date,uint8_t cmd){
    if(NULL == sCardOps){
        CARD_LOG_DEBUG(TAG,"sCardOps is NULL,Check sCardInit");
        return -1;
    }
    if(sCardWaitReady() != 0){
        return 1;
    }
    uint16_t i = 0;
    int8_t *w_date = (int8_t *)date;
    sCardOps->sCardReadWrite(cmd);
    if(cmd != sCARD_MULTI_BLOCK_WRITE){
        for(;i < sCARD_BLOCK_SIZE;i ++){
            sCardOps->sCardReadWrite(w_date[i]);
        }
        sCardOps->sCardReadWrite(sCARD_DUMMY_BYTE);
        sCardOps->sCardReadWrite(sCARD_DUMMY_BYTE);
        i = sCardOps->sCardReadWrite(sCARD_DUMMY_BYTE);
        if((i & 0x1F) != 0x05){
            return 2;
        }
    }
    return 0;
}

/**
 * @brief sCard Write Command
 * 
 * @param cmd write Command
 * @param arg write arg
 * @param crc write crc
 * @return int8_t 
 */
static int8_t sCardWriteCommand(sCardCommand_t cmd,uint32_t arg,uint8_t crc){
    if(NULL == sCardOps){
        CARD_LOG_DEBUG(TAG,"sCardOps is NULL,Check sCardInit");
        return -1;
    }
    int8_t res;
    uint8_t retry = 0;
    sCardDisable();
    if(sCardEnable() != 0){
        return 0xFF;
    }
    sCardOps->sCardReadWrite(cmd | 0x40);
    sCardOps->sCardReadWrite(arg >> 24);
    sCardOps->sCardReadWrite(arg >> 16);
    sCardOps->sCardReadWrite(arg >> 8);
    sCardOps->sCardReadWrite(arg);
    sCardOps->sCardReadWrite(crc);

    if(cmd == sCARD_COMMAND_12){
        sCardOps->sCardReadWrite(sCARD_DUMMY_BYTE);
    }
    retry = 0x1F;
    for(;retry;retry --){
        res = sCardOps->sCardReadWrite(sCARD_DUMMY_BYTE);
        if((res & 0x80) == 0){
            break;
        }
    }
    return res;
}

/**
 * @brief sCard Get Cid
 * 
 * @param date cid Date buffer
 * @return int8_t 
 */
int8_t sCardGetCid(void *date){
    if(NULL == sCardOps){
        CARD_LOG_DEBUG(TAG,"sCardOps is NULL,Check sCardInit");
        return -1;
    }
    int8_t res;
    res = sCardWriteCommand(sCARD_COMMAND_10,0,0x01);
    if(res == 0x00){
        res = sCardReadDate(date,16);
    }
    sCardDisable();
    if(res != 0){
        CARD_LOG_DEBUG(TAG,"sCard Get Cid Fail");
        return 1;
    }
    return 0;
}

/**
 * @brief sCard Get Csd
 * 
 * @param date csd date buffer
 * @return int8_t 
 */
static int8_t sCardGetCsd(void *date){
    if(NULL == sCardOps){
        CARD_LOG_DEBUG(TAG,"sCardOps is NULL,Check sCardInit");
        return -1;
    }
    int8_t res;
    res = sCardWriteCommand(sCARD_COMMAND_9,0,0x01);
    if(res == 0){
        res = sCardReadDate(date,16);
    }
    sCardDisable();
    if(res != 0){
        CARD_LOG_DEBUG(TAG,"sCard Get Csd Fail");
        return 1;
    }
    return 0;
}

/**
 * @brief Get sCard Sector Size
 * 
 * @return int32_t 
 */
int32_t sCardGetSectorSize(void){
    if(NULL == sCardOps){
        CARD_LOG_DEBUG(TAG,"sCardOps is NULL,Check sCardInit");
        return -1;
    }
    uint8_t csd[16] = {0};
    uint32_t capacity = 0;
    uint8_t n = 0;
    uint16_t  c_size = 0;

    if(sCardGetCsd(csd) != 0){
        return 0;
    }
    if((csd[0] & 0xC0) == 0x40){ //v2.0 card
        c_size = csd[9] + ((uint16_t)csd[8] << 8) + 1;
        capacity = (uint32_t)c_size << 10;
    }else{
        n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
		c_size = (csd[8] >> 6) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 3) << 10) + 1;
		capacity = (uint32_t)c_size << (n - 9); /* get sector number size*/
    }
    return capacity;
}

/**
 * @brief sCard Read date
 * 
 * @param date date buffer
 * @param sector read Sector
 * @param size read sector size
 * @return int8_t 
 */
int8_t sCardRead(void *date,uint32_t sector,uint8_t size){
    if(NULL == sCardOps){
        CARD_LOG_DEBUG(TAG,"sCardOps is NULL,Check sCardInit");
        return -1;
    }
    int8_t res;
    if(sCardOps->sCardType != sCARD_TYPE_V2HC){
        sector <<= 9;
    }
    int8_t *r_date = (int8_t *)date;
    /*continue read date*/
    if(size == 1){
        res = sCardWriteCommand(sCARD_COMMAND_17,sector,0x01);
        if(res == 0){
            res = sCardReadDate(r_date,sCARD_BLOCK_SIZE);
        }
    }else{
        res = sCardWriteCommand(sCARD_COMMAND_18,sector,0x01);
        for(int i = 0;((i < size) && (res == 0));i ++){
            res = sCardReadDate(r_date,sCARD_BLOCK_SIZE);
            r_date += sCARD_BLOCK_SIZE;
        }
        sCardWriteCommand(sCARD_COMMAND_12,0,0x01);
    }
    sCardDisable();
    return res;
}

/**
 * @brief sCard Write Date
 * 
 * @param date write Date
 * @param sector write Sector
 * @param size write Sector size
 * @return int8_t 
 */
int8_t sCardWrite(void *date,uint32_t sector,uint8_t size){
    if(NULL == sCardOps){
        CARD_LOG_DEBUG(TAG,"sCardOps is NULL,Check sCardInit");
        return -1;
    }
    int8_t res;
    int8_t *date_p = (int8_t *)date;
    if(sCardOps->sCardType != sCARD_TYPE_V2HC){
        sector <<= 9;
    }
    if(size == 1){
        res = sCardWriteCommand(sCARD_COMMAND_24,sector,0x01);
        if(res == 0){
            res = sCardWriteDate(date_p,sCARD_SINGLE_BLOCK_WRITE);
        }
    }else{
        if(sCardOps->sCardType != sCARD_TYPE_MMC){
            sCardWriteCommand(sCARD_COMMAND_55,0,0x01);
            sCardWriteCommand(sCARD_COMMAND_23,size,0x01);
        }
        res = sCardWriteCommand(sCARD_COMMAND_25,sector,0x01);
        if(res == 0){
            for(int i = 0;((i < size) && (res == 0));i ++){
                res = sCardWriteDate(date_p,0xFC);
                date_p += sCARD_BLOCK_SIZE;
            }
            res = sCardWriteDate(0,sCARD_MULTI_BLOCK_WRITE);
        }
    }
    sCardDisable();
    return res;
}

/**
 * @brief sCard Get Card Type
 * 
 * @return int8_t 
 */
static int8_t sCardGetCardType(void){
    if(NULL == sCardOps){
        CARD_LOG_DEBUG(TAG,"sCardOps is NULL,Check sCardInit");
        return -1;
    }
    sCardSetSpeedLow();
    /*send 72 tick*/
    for(int i = 0;i < 10;i ++){
        sCardOps->sCardReadWrite(sCARD_DUMMY_BYTE);
    }
    uint8_t buffer[4] = {0};
    uint16_t retry = 20;
    int8_t res = 0;
    int8_t resStatus = 0;
    int t = 0;
    for(;((retry > 0) && (res != 0x01));retry --){
        res = sCardWriteCommand(sCARD_COMMAND_0,0,0x95);
    }
    if(retry == 0){
        return -1;
    }
    sCardOps->sCardType = sCARD_TYPE_ERROR;
    if(res == 0x01){
        if(sCardWriteCommand(sCARD_COMMAND_8,0x1AA,0x87) == 0x01){
            /*card version v2.0*/
            for(t = 0;t < 4;t ++){
                buffer[t] = sCardOps->sCardReadWrite(sCARD_DUMMY_BYTE);
            }
            /*sd card isnot surpport 2.7 ~ 3.6V*/
            if((buffer[2] == 0x01) && (buffer[3] == 0xAA)){
                /*wait enter idle mode*/
                retry = 0xFFFE;
                for(;((retry > 0) && (res != 0));retry --){
                    sCardWriteCommand(sCARD_COMMAND_55,0,0x01);
                    res = sCardWriteCommand(sCARD_COMMAND_41,0x40000000,0x01);
                }
                /*get card device info*/
                res = sCardWriteCommand(sCARD_COMMAND_58,0,0x01);
                if((res == 0) && (retry > 0)){
                    for(t = 0;t < 4;t ++){
                        buffer[t] = sCardOps->sCardReadWrite(sCARD_DUMMY_BYTE);
                    }
                    if(buffer[0] & 0x40){
                        sCardOps->sCardType = sCARD_TYPE_V2HC;
                    }else{
                        sCardOps->sCardType = sCARD_TYPE_V2;
                    }
                }else{
                    resStatus = -1;
                }
            }else{
                /*error type*/
                resStatus = -1;
            }
        }else{
            resStatus = -1;
        }
    }else{
        /*sdcard v1.x or V3*/
        sCardWriteCommand(sCARD_COMMAND_55,0,0x01);
        res = sCardWriteCommand(sCARD_COMMAND_41,0,0x01);
        if(res <= 1){
            sCardOps->sCardType = sCARD_TYPE_V1;
            /*wait enter idle mode*/
            retry = 0xFFFE;
            for(;((retry > 0) && (res != 0));retry --){
                sCardWriteCommand(sCARD_COMMAND_55,0,0x01);
                res = sCardWriteCommand(sCARD_COMMAND_41,0x40000000,0x01);
            }
        }else{
            sCardOps->sCardType = sCARD_TYPE_MMC;
            for(;((retry > 0) && (res != 0));retry --){
                sCardWriteCommand(sCARD_COMMAND_55,0,0x01);
                res = sCardWriteCommand(sCARD_COMMAND_41,0x40000000,0x01);
            }
        }
        res = sCardWriteCommand(sCARD_COMMAND_16,sCARD_BLOCK_SIZE,0x01);
        if((retry == 0) || (res != 0)){
            sCardOps->sCardType = sCARD_TYPE_ERROR;
            resStatus = -1;
        }
    }
    sCardDisable();
    sCardSetSpeedHigh();
    return resStatus;
}

/**
 * @brief sCard Init
 * 
 * @return int8_t 
 */
int8_t sCardInit(void){
    int8_t status = 0;
    sCardOps = &sCardOpsStruct;

    sCardOps->sCardInit();
	sCardOps->sCardStatusSet(1);
    status = sCardGetCardType();
	if(status == 0){
        return 0;
    }
    return -1;
}

#endif
